﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Linq;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using GraphLib;
using WindowsFormsApplication2.Properties;

namespace WindowsFormsApplication2
{
    public partial class GraphForm : Form
    {
        #region Инициализация переменных и конструктор
        private Graph _graph;
        private bool flagLeftMouseDown;
        private Timer _timerToRefresh;
        private string[] _algorithmsArray;
        private List<GraphPoint> _solution;
        private List<List<GraphPoint>> _multipleSolution;
        private int _maxFlow;
        private List<GraphLink> _multipleMaxFlows;
        private string lastUsedFile;

        #region Константы
        //Диапазон, в котором будет генерироваться произвольная точка
        private const int _rangeForPoitX = 620;
        private const int _rangeForPoitY = 230;
        #endregion

        public GraphForm()
        {
            InitializeComponent();
            InitComp();
        }

        private void InitComp()
        {
            linksDGV.AutoGenerateColumns = false;
            
            #region Назначение обработчиков событий
            glControl1.Paint += glControl1_Paint;
            glControl1.Resize += glControl1_Resize;
            glControl1.MouseDown += glControl1_MouseDown;
            glControl1.MouseMove += glControl1_MouseMove;
            glControl1.MouseUp += glControl1_MouseUp;
            addPoint_Button.Click += addRandomPoint_Button_Click;
            delLink_Button.Click += DelLinkButtonClick;
            addLink_Button.Click += AddLinkButtonClick;
            delPoint_Button.Click += delPointButton_Click;
            Load += Form1_Load;
            changeSplitDistance_Button.Click += changeSplitDistance_Button_Click;
            refresh_Button.Click += refresh_Button_Click;
            findShortPath_Button.Click += findShortPath_Button_Click;
            clearResult_Button.Click += clearResult_Button_Click;
            loadGraphToolStripMenuItem.Click += loadGraphToolStripMenuItem_Click;
            saveGraphToolStripMenuItem.Click += saveGraphToolStripMenuItem_Click;
            saveGraphAsToolStripMenuItem.Click += saveGraphAsToolStripMenuItem_Click;
            exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
            autoNamePoint_CheckBox.CheckStateChanged += AutoNamePoint_CheckBox_CheckStateChanged;
            loadExampleToolStripMenuItem.Click += loadExampleToolStripMenuItem_Click;
            newGraphToolStripMenuItem.Click += newGraphToolStripMenuItem_Click;
            aboutProgramToolStripMenuItem.Click += aboutProgramToolStripMenuItem_Click;
            #endregion

            #region BackgroundImage Buttons
            changeSplitDistance_Button.BackgroundImage = Resources.arrow_up_default;
            addPoint_Button.BackgroundImage = Resources.создать;
            delPoint_Button.BackgroundImage = Resources.удалить;
            delLink_Button.BackgroundImage = Resources.удалить;
            addLink_Button.BackgroundImage = Resources.создать;
            refresh_Button.BackgroundImage = Resources.refresh;
            #endregion

            #region ToolTips
            var t = new ToolTip();
            t.SetToolTip(addPoint_Button, "Добавить точку");
            t.SetToolTip(delPoint_Button, "Удалить точку");
            t.SetToolTip(addLink_Button, "Добавить дугу");
            t.SetToolTip(delLink_Button, "Удалить дугу");
            t.SetToolTip(changeSplitDistance_Button, "Показать/скрыть граф");
            t.SetToolTip(refresh_Button, "Обновить");
            #endregion

            _algorithmsArray = new[]
                                   {
                                       "Задача о кратчайшей цепи (Дейкстра)",
                                       "Задача о многополюсной кратчайшей цепи",
                                       "Задача о максимальном потоке",
                                       "Задача о многополюсном максимальном потоке"
                                   };

            BlockUserInput(true);

            #region TimerToRefresh
            _timerToRefresh = new Timer { Interval = 1, Enabled = true };
            _timerToRefresh.Tick += TimerToRefreshTick;
            #endregion
        }
        #endregion

        #region Fill Listboxes and ComboBoxex
        /// <summary>
        /// Распарсить граф по графическим компонентам
        /// </summary>
        private void ParseGraph()
        {
            Points_ListBox.DataSource = _graph.Points;

            linksDGV.DataSource = _graph.Links;
            Column1.DataPropertyName = "Name";
            Column2.DataPropertyName = "Weight";

            FillPointsAddComboBox();
            FillAlgorithmComboBox();
        }

        private void FillPointsAddComboBox()
        {
            startPoint_ComboBox.Items.Clear();
            endPoint_ComboBox.Items.Clear();
            pointSource_ComboBox.Items.Clear();
            pointRunoff_ComboBox.Items.Clear();

            foreach (var item in _graph.Points)
            {
                startPoint_ComboBox.Items.Add(item);
                endPoint_ComboBox.Items.Add(item);
                pointSource_ComboBox.Items.Add(item);
                pointRunoff_ComboBox.Items.Add(item);
            }
        }

        private void FillAlgorithmComboBox()
        {
            algorithms_ComboBox.Items.Clear();
            algorithms_ComboBox.Items.AddRange(_algorithmsArray);
        }
        #endregion
        
        #region Обновление графа
        public void GlInit()
        {
            GL.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);

            int w = glControl1.Width;
            int h = glControl1.Height;
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(0, w, 0, h, -1, 1);
            GL.Viewport(0, 0, w, h);

            GL.Enable(EnableCap.Texture2D);
            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
        }
        
        private void RefreshGraph()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);


            if (showSolution_checkBox.Checked && _solution != null)
            {
                GL.LineWidth(4);
                GL.Color3(0.7f, 0.7f, 1.0f);
                GL.Begin(BeginMode.LineStrip);
                foreach (var item in _solution)
                {
                    GL.Vertex2(item.X, item.Y);
                }
                GL.End();
            }
            
            if(_graph == null) return;
            _graph.Draw();
            
            if (Points_ListBox.SelectedIndex > -1)
            {
                GraphPoint gp = (GraphPoint) Points_ListBox.SelectedItem;
                if (gp == null)
                {
                    GlInit();
                    return;
                }
                GL.PointSize(8);
                GL.Color4(Color4.Red);
                GL.Begin(BeginMode.Points);
                GL.Vertex2(gp.X, gp.Y);
                GL.End();
            }

            glControl1.SwapBuffers();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            GlInit();
        }

        private void glControl1_Resize(object sender, EventArgs e)
        {
            GlInit();
        }

        void refresh_Button_Click(object sender, EventArgs e)
        {
            RefreshGraph();
        }

        private void glControl1_Paint(object sender, PaintEventArgs e)
        {
            RefreshGraph();
        }

        private void TimerToRefreshTick(object sender, EventArgs e)
        {
            RefreshGraph();
        }
        #endregion

        #region Перемещение точек графа: MouseDown, MouseUp, MouseMove
        private void glControl1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                flagLeftMouseDown = true;

                if (Points_ListBox.SelectedItem != null)
                {
                    GraphPoint gp = _graph.Points[Points_ListBox.SelectedIndex];
                    gp.X = e.X;
                    gp.Y = glControl1.Height - e.Y;
                }
            }
        }

        private void glControl1_MouseMove(object sender, MouseEventArgs e)
        {
            if (flagLeftMouseDown)
            {
                if (Points_ListBox.SelectedItem != null)
                {
                    GraphPoint gp = _graph.Points[Points_ListBox.SelectedIndex];
                    gp.X = e.X;
                    gp.Y = glControl1.Height - e.Y;
                }
            }
        }

        private void glControl1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                flagLeftMouseDown = false;
        }
        #endregion

        #region Добавление/удаление точек
        private void addPointButton_Click(object sender, EventArgs e)
        {
            if (namePoint_TextBox.Text.Equals(string.Empty))
            {
                MessageBox.Show(@"Невозможно добавить точку. Не заполнены необходимые данные");
                return;
            }
            if (_graph.GetPointByName(namePoint_TextBox.Text) != null)
            {
                MessageBox.Show(@"Невозможно добавить точку. Точка с таким именем уже существует");
                return;
            }
            _graph.Points.Add(new GraphPoint(new Random().Next(_rangeForPoitX), new Random().Next(_rangeForPoitY), namePoint_TextBox.Text));
            FillPointsAddComboBox();
        }

        void addRandomPoint_Button_Click(object sender, EventArgs e)
        {
            string pointNamePrefix = "RndPoint_";
            bool IsUniqName = false;
            int i = 0;
            while (!IsUniqName)
            {
                i++;
                if (_graph.GetPointByName(pointNamePrefix + i.ToString()) == null)
                    IsUniqName = true;
            }
            _graph.Points.Add(new GraphPoint(new Random().Next(_rangeForPoitX), new Random().Next(_rangeForPoitY), pointNamePrefix + i.ToString()));
            Points_ListBox.SelectedIndex = Points_ListBox.Items.Count - 1;
            FillPointsAddComboBox();
        }

        private void delPointButton_Click(object sender, EventArgs e)
        {
            if (Points_ListBox.SelectedItem == null) return;
            _graph.DeleteUnusedLinks(_graph.Points[Points_ListBox.SelectedIndex]);
            _graph.Points.RemoveAt(Points_ListBox.SelectedIndex);
        }

        void AutoNamePoint_CheckBox_CheckStateChanged(object sender, EventArgs e)
        {
            switch (autoNamePoint_CheckBox.Checked)
            {
                case true:
                    namePoint_Label.Visible = false;
                    namePoint_TextBox.Visible = false;
                    addPoint_Button.Click -= addPointButton_Click;
                    addPoint_Button.Click += addRandomPoint_Button_Click;
                    break;
                case false:
                    namePoint_Label.Visible = true;
                    namePoint_TextBox.Visible = true;
                    addPoint_Button.Click -= addRandomPoint_Button_Click;
                    addPoint_Button.Click += addPointButton_Click;
                    break;
            }
        }
        #endregion

        #region Добавление/удаление дуг
        private void DelLinkButtonClick(object sender, EventArgs e)
        {
            if (linksDGV.CurrentCell == null) return;
            _graph.Links.RemoveAt(linksDGV.CurrentCell.RowIndex);
        }

        private void AddLinkButtonClick(object sender, EventArgs e)
        {
            if (startPoint_ComboBox.SelectedItem == null || endPoint_ComboBox.SelectedItem == null)
            {
                MessageBox.Show(@"Выберите начальную и конечную точки дуги");
                return;
            }
            _graph.Links.Add(new GraphLink((GraphPoint)startPoint_ComboBox.SelectedItem, (GraphPoint)endPoint_ComboBox.SelectedItem, orientedLink_CheckBox.Checked ? LinkDirection.LdStraight : LinkDirection.LdBoth, (int)weight_numericUpDown.Value));
        }
        #endregion

        #region Ввод в TextBox-ы только цифр
        private void KeyPressOnlyDigits(Object textBox, KeyPressEventArgs eventArgs)
        {
            if (!char.IsControl(eventArgs.KeyChar) && !char.IsDigit(eventArgs.KeyChar) && eventArgs.KeyChar != '.')
                eventArgs.Handled = true;

            // only allow one decimal point
            if (eventArgs.KeyChar == '.' && ((TextBox)textBox).Text.IndexOf('.') > -1)
                eventArgs.Handled = true;
        }
        
        void coordinateX_TextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            KeyPressOnlyDigits(sender, e);
        }

        void coordinateY_TextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            KeyPressOnlyDigits(sender, e);
        }
        #endregion

        #region Работа алгоритмов
        void findShortPath_Button_Click(object sender, EventArgs e)
        {
            _solution = null;
            _multipleSolution = null;
            _maxFlow = -1;
            _multipleMaxFlows = null;
            var pointSource = (GraphPoint) pointSource_ComboBox.SelectedItem;
            var pointRunoff = (GraphPoint) pointRunoff_ComboBox.SelectedItem;

            switch (algorithms_ComboBox.SelectedIndex)
            {
                case 0:
                    {
                        if (pointSource_ComboBox.SelectedItem == null || pointRunoff_ComboBox.SelectedItem == null || algorithms_ComboBox.SelectedItem == null)
                        {
                            MessageBox.Show(@"Пожалуйста, выберите точку источник, точку сток и алгоритм");
                            return;
                        }
                        _solution = Algorithms.Algorithm1_Go(_graph, pointSource, pointRunoff);
                        DisplayResult(_solution);
                        break;
                    }
                case 1:
                    {
                        _multipleSolution = Algorithms.Algorithm2_Go(_graph);
                        DisplayResult(_multipleSolution);
                        break;
                    }
                case 2:
                    {
                        _maxFlow = Algorithms.Algorithm3_Go(_graph, pointSource, pointRunoff);
                        DisplayResult(_maxFlow);
                        break;
                    }
                case 3:
                    {
                        _multipleMaxFlows = Algorithms.Algorithm4_Go(_graph);
                        DisplayResult(_multipleMaxFlows);
                        break;
                    }
                default:
                    {
                        MessageBox.Show(@"Невозможно найти кратчайший путь данным алгоритмом");
                        break;
                    }
            }
        }
        #endregion

        #region Вспомогательные методы
        /// <summary>
        /// Блокировать пользовательский ввод данных
        /// </summary>
        /// <param name="statusBlock">Статус блокировки</param>
        private void BlockUserInput(bool statusBlock)
        {
            points_TabPage.Enabled = !statusBlock;
            links_TabPage.Enabled = !statusBlock;
            graph_GroupBox.Enabled = !statusBlock;
        }
        
        void changeSplitDistance_Button_Click(object sender, EventArgs e)
        {
            var distance = splitContainer1.SplitterDistance;
            if (distance > -1 && distance < 81)
            {
                splitContainer1.SplitterDistance = 320;
                changeSplitDistance_Button.BackgroundImage = Resources.arrow_up_default;
            }
            else
            {
                splitContainer1.SplitterDistance = 80;
                changeSplitDistance_Button.BackgroundImage = Resources.arrow_down_default;
            }
        }
        
        /// <summary>
        /// Вывести результат в TextBox, представленный в виде списка точек
        /// </summary>
        /// <param name="listPoints">Лист точек кратчайшего пути</param>
        private void DisplayResult(List<GraphPoint> listPoints)
        {
            shortestPath_TextBox.Text = "Решение невозможно вычислить!!!";
            if (listPoints == null) return;
            var stringBuilder = new StringBuilder();
            foreach (var point in listPoints)
            {
                stringBuilder.Append(point.Name);
                stringBuilder.Append(", ");
            }
            shortestPath_TextBox.Text = stringBuilder.ToString();
        }

        private void DisplayResult(List<List<GraphPoint>> listPaths)
        {
            shortestPath_TextBox.Text = "Решение невозможно вычислить!!!";
            if (listPaths == null) return;

            shortestPath_TextBox.Text = "";
            foreach (var path in listPaths)
            {
                String str = "";
                if (path != null)
                {
                    foreach (var point in path)
                    {
                        str += point.Name + ", ";
                    }
                    shortestPath_TextBox.Text += str + Environment.NewLine;
                }
            }
        }

        private void DisplayResult(int maxFlow)
        {
            shortestPath_TextBox.Text = "Решение невозможно вычислить!!!";
            if (maxFlow == -1) return;

            shortestPath_TextBox.Text = "Максимальный поток: " + maxFlow.ToString();
        }

        private void DisplayResult(List<GraphLink> listFlows)
        {
            shortestPath_TextBox.Text = "Решение невозможно вычислить!!!";
            if (listFlows == null) return;

            shortestPath_TextBox.Text = "";
            foreach (var flow in listFlows)
            {
                if (flow != null)
                {
                    if (flow.Weight > 0)
                    {
                        shortestPath_TextBox.Text += flow.Weight.ToString() + "\t" + flow.ToString() + Environment.NewLine;
                    }
                }
            }
        }

        void clearResult_Button_Click(object sender, EventArgs e)
        {
            shortestPath_TextBox.Text = string.Empty;
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        void newGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _graph = new Graph();
            ParseGraph();
            BlockUserInput(false);
        }

        void aboutProgramToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("GraphProject v 0.1\nПрограмма для работы с графами\n\nРАЗРАБОТЧИКИ\nФакультет: ЕГФ\nМагистры группы: ИСм-111");
        }
        #endregion

        #region Загрузка/сохранение графа через XML
        /// <summary>
        /// Загрузить граф из XML-файла
        /// </summary>
        private void loadGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var od = new OpenFileDialog {Filter = "XML|*.xml"};
            if (od.ShowDialog().Equals(DialogResult.OK))
            {
                try
                {
                    var doc = XDocument.Load(od.FileName);
                    _graph = new Graph();
                    var points = doc.Root.Element("points");
                    var links = doc.Root.Element("links");
                    foreach(var point in points.Elements())
                        _graph.Points.Add(new GraphPoint
                        {
                            Name = point.Attribute("name").Value,
                            Mark = Convert.ToInt32(point.Attribute("mark").Value),
                            ConstantMark = Convert.ToBoolean(point.Attribute("constantMark").Value),
                            X = Convert.ToInt32(point.Attribute("x").Value),
                            Y = Convert.ToInt32(point.Attribute("y").Value)
                        });
                    foreach (var link in links.Elements())
                        _graph.Links.Add(new GraphLink
                        {
                            P1 = _graph.Points.FirstOrDefault(point=>point.Name.Equals(link.Attribute("p1").Value)),
                            P2 = _graph.Points.FirstOrDefault(point => point.Name.Equals(link.Attribute("p2").Value)),
                            Ld = (LinkDirection) Convert.ToInt32(link.Attribute("ld").Value),
                            Weight = Convert.ToInt32(link.Attribute("weight").Value)
                        });
                    ParseGraph();
                    lastUsedFile = od.FileName;

                    BlockUserInput(false);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Файл не может быть открыт.\n"+ex.Message);
                }
            }
        }

        /// <summary>
        /// Сохранить граф как
        /// </summary>
        private void saveGraphAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var sd = new SaveFileDialog { Filter = "XML|*.xml" };
            if (sd.ShowDialog().Equals(DialogResult.OK))
                SaveCurrentGraph(sd.FileName);
        }

        /// <summary>
        /// Сохранить текущий граф в XML-файл
        /// </summary>
        /// <param name="fileName">Имя XML-файла</param>
        private void SaveCurrentGraph(string fileName)
        {
            try
            {
                var doc = new XDocument();
                doc.Add(new XElement("graph"));
                var points = new XElement("points");
                doc.Root.Add(points);
                foreach (var graphPoint in _graph.Points)
                {
                    var point = new XElement("point");
                    points.Add(point);
                    point.Add(new XAttribute("name", graphPoint.Name));
                    point.Add(new XAttribute("x", graphPoint.X));
                    point.Add(new XAttribute("y", graphPoint.Y));
                    point.Add(new XAttribute("mark", graphPoint.Mark));
                    point.Add(new XAttribute("constantMark", graphPoint.ConstantMark));
                }
                var links = new XElement("links");
                doc.Root.Add(links);
                foreach (var graphLink in _graph.Links)
                {
                    var link = new XElement("link");
                    links.Add(link);
                    link.Add(new XAttribute("p1", graphLink.P1.Name));
                    link.Add(new XAttribute("p2", graphLink.P2.Name));
                    link.Add(new XAttribute("ld", (int) graphLink.Ld));
                    link.Add(new XAttribute("weight", graphLink.Weight));
                }
                doc.Save(fileName);
                lastUsedFile = fileName;
                MessageBox.Show("Сохранение прошло успешно.\nВ файл: " + fileName);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Невозможно произвести сохранение в файл:\n" + fileName+"\n" + ex.Message);
            }
        }

        /// <summary>
        /// Сохранить граф
        /// </summary>
        private void saveGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(lastUsedFile))
                saveGraphAsToolStripMenuItem_Click(null, null);
            else
                SaveCurrentGraph(lastUsedFile);
        }

        /// <summary>
        /// Загрузить пример
        /// </summary>
        void loadExampleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            #region Инициализация графа (ОБРАЗЕЦ)
            _graph = new Graph();
            _graph.Points.Add(new GraphPoint(100, 100, "P1"));
            _graph.Points.Add(new GraphPoint(200, 160, "P2"));
            _graph.Points.Add(new GraphPoint(200, 40, "P3"));
            _graph.Points.Add(new GraphPoint(270, 160, "P4"));
            _graph.Points.Add(new GraphPoint(270, 40, "P5"));
            _graph.Points.Add(new GraphPoint(360, 140, "P6"));
            _graph.Points.Add(new GraphPoint(400, 100, "P7"));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P1"), _graph.GetPointByName("P2"), LinkDirection.LdStraight, 2));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P1"), _graph.GetPointByName("P3"), LinkDirection.LdStraight, 1));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P2"), _graph.GetPointByName("P4"), LinkDirection.LdStraight, 3));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P3"), _graph.GetPointByName("P5"), LinkDirection.LdStraight, 4));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P4"), _graph.GetPointByName("P6"), LinkDirection.LdStraight, 1));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P6"), _graph.GetPointByName("P7"), LinkDirection.LdStraight, 2));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P5"), _graph.GetPointByName("P7"), LinkDirection.LdStraight, 5));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P2"), _graph.GetPointByName("P5"), LinkDirection.LdStraight, 6));
            _graph.Links.Add(new GraphLink(_graph.GetPointByName("P5"), _graph.GetPointByName("P4"), LinkDirection.LdStraight, 7));
            #endregion

            ParseGraph();

            #region Выбор значений в ComboBox-ах (ОБРАЗЕЦ)
            pointSource_ComboBox.SelectedIndex = 0;
            pointRunoff_ComboBox.SelectedIndex = 6;
            algorithms_ComboBox.SelectedIndex = 0;
            #endregion

            BlockUserInput(false);
        }
        #endregion
    }
}
